/*
 * ObjectManager.cpp
 *
 *  Created on: Sep 7, 2013
 *      Author: jason
 */

#include <algorithm>    // std::sort
#include <SFML/Graphics.hpp>
#include <vector>
#include "../../include/GameModes/ObjectManager.hpp"
#include "../../include/Common.hpp"

namespace OForce_Mode {

ObjectManager::~ObjectManager()
{
	for (int i = 0; i < vObject.size(); i++) {
		delete vObject[i];
	}
	vObject.clear();
	vTreasure.clear();
	vCharacters.clear();
	for (int i = 0; i < vTrigger.size(); i++) {
		delete vTrigger[i];
	}
	vTrigger.clear();
}

void ObjectManager::sortObjects() {
	std::sort(vCharacters.begin(), vCharacters.end(), Char_LessThanLayer());
}

void ObjectManager::update() {
	for (int i = 0; i < vCharacters.size(); i++) {
		vCharacters[i]->update();
	}
	for (int i = 0; i < vTrigger.size(); i++) {
		vTrigger[i]->update();
	}
}

void ObjectManager::drawLayer(int l) {
	for (int i = 0; i < vCharacters.size(); i++) {
		if (vCharacters[i]->getCurrentLayer() == l) {
			vCharacters[i]->draw();
		}
	}
	for (int i = 0; i < vTreasure.size(); i++) {
		if (vTreasure[i]->getCurrentLayer() == 0) {
			vTreasure[i]->draw();
		}
	}
}

void ObjectManager::draw() {
	for (int i = 0; i < vCharacters.size(); i++) {
		vCharacters[i]->draw();
	}
}

OForce_Object::Object *ObjectManager::getObject(int id)
{
	std::unordered_map<int, OForce_Object::Object *>::iterator it = objectMap.find(id);
	if (it == objectMap.end()) {
		return NULL;
	} else {
		return it->second;
	}
}

OForce_Object::Character *ObjectManager::getCharacter(int id)
{
	std::unordered_map<int, OForce_Object::Object *>::iterator it = objectMap.find(id);
	if (it == objectMap.end()) {
		return NULL;
	} else {
		OForce_Object::Character *character =
				dynamic_cast<OForce_Object::Character *> ((*it).second);
		if (character == NULL) {
			std::cerr << "Error: Object with ID, " << id << ", is not a character.\n";
			return NULL;
		}
		return character;
	}
}

OForce_Object::Object *ObjectManager::findInteractable(OForce_Object::Character *character,
		float searchDistance) {
	int charLayer = character->getCurrentLayer();
	sf::FloatRect searchArea;
	sf::Vector2f charPos = character->sprite.getPosition();
	sf::Vector2i charSize = character->getSize();
	OForce_Object::DIRECTION dir = character->getDirection();
	if (dir == OForce_Object::DOWN) {
		searchArea = sf::FloatRect(charPos.x,charPos.y+charSize.y,charSize.x,searchDistance);
	} else if (dir == OForce_Object::LEFT) {
		searchArea = sf::FloatRect(charPos.x-searchDistance,charPos.y,searchDistance,charSize.y);
	} else if (dir == OForce_Object::RIGHT) {
		searchArea = sf::FloatRect(charPos.x+charSize.x,charPos.y,searchDistance,charSize.y);
	} else if (dir == OForce_Object::UP) {
		searchArea = sf::FloatRect(charPos.x,charPos.y-searchDistance,charSize.x,searchDistance);
	}
	std::vector<OForce_Object::Object *> touchedObjects;
	for (std::vector<OForce_Object::Object *>::iterator it=vObject.begin();
			it != vObject.end(); it++) {
		// Don't bother checking yourself
		if (*it != character) {
			if ((*it)->sprite.getGlobalBounds().intersects(searchArea)) {
				touchedObjects.push_back(*it);
			}
		}
	}
	if (touchedObjects.empty()) {
		return NULL;
	} else if (touchedObjects.size() == 1) {
		// Only one touched object
		return touchedObjects[0];
	}
	OForce_Object::Object *closest = touchedObjects[0];
	// dist is sqrt(dx^2+dy^2) but there's no reason to do that
	// for our purposes, abs(dx)+abs(dy) is good enough
	float minDist = fabs(charPos.x-closest->sprite.getPosition().x) +
			fabs(charPos.y-closest->sprite.getPosition().y);
	for (int i = 1; i < touchedObjects.size(); i++) {
		float dist = fabs(charPos.x-touchedObjects[i]->sprite.getPosition().x) +
				fabs(charPos.y-touchedObjects[i]->sprite.getPosition().y);
		if (dist < minDist) {
			closest = touchedObjects[i];
			minDist = dist;
		}
	}
	return closest;
}

// Use A-star algorithm to find optimal path
bool ObjectManager::findPath(OForce_Object::Character *character,
			std::vector<sf::Vector2f> &path, sf::Vector2f dest)
{
	// TODO all of this is assuming a tile size of 32
	static const int basic_gcost = 10;
	int dx, dy;
	// Make sure the path is empty and ready to be remade
	path.clear();
	PathNode sourceNode((int)character->getXPosition()/32, (int)character->getYPosition()/32);
	PathNode destNode((int)(dest.x/32), (int)(dest.y/32));
	if (sourceNode == destNode) {
		std::cerr << "Source and destination are the same.\n";
		return false;
	}
	std::vector<PathNode> openList;
	std::vector<PathNode> closedList;
	PathNode bestNode;
	// storage for the nodes in up-down-left-right directions
	PathNode nodes[4];
	int gAdd;
	openList.push_back(sourceNode);
	while (!openList.empty()) {
		std::sort(openList.begin(), openList.end());
		bestNode = openList.back();
		openList.pop_back();
		closedList.push_back(bestNode);
		if (bestNode == destNode) {
			break;
		}
		nodes[0].x = bestNode.x-1;
		nodes[0].y = bestNode.y;
		nodes[1].x = bestNode.x+1;
		nodes[1].y = bestNode.y;
		nodes[2].x = bestNode.x;
		nodes[2].y = bestNode.y-1;
		nodes[3].x = bestNode.x;
		nodes[3].y = bestNode.y+1;
		for (int i = 0; i < 4; i++) {
			// Check if there is a collision
			// TODO Need to put in check for if the block is a character so the game doesn't lock up.
			COLLISION_TYPE ct = collisionCheck(character, (float)nodes[i].x*32, (float)nodes[i].y*32);
			// handle collision with unmoving object
			if (ct == STATIC) {
				continue;
			}
			gAdd = basic_gcost;
			if (ct == CHARACTER) {
				gAdd += basic_gcost*2;
			}
			// if node exists in the list already, skip it.
			if (std::find(closedList.begin(), closedList.end(), nodes[i]) != closedList.end()) {
				continue;
			}
			// save parent information
			nodes[i].parent_x = bestNode.x;
			nodes[i].parent_y = bestNode.y;
			nodes[i].g		  = bestNode.g + gAdd;
			// find out if node is in the openList already
			std::vector<PathNode>::iterator it = std::find(openList.begin(), openList.end(), nodes[i]);
			if (it != openList.end()) {
				// if the g-score for the earlier version is higher, the current path is better
				if (it->g > nodes[i].g) {
					it->g = nodes[i].g;
					it->f = nodes[i].g + it->h;
					it->parent_x = nodes[i].parent_x;
					it->parent_y = nodes[i].parent_y;
				}
			} else {
				// add new node to open list
				dx = abs(destNode.x - nodes[i].x);
				dy = abs(destNode.y - nodes[i].y);
				/*if (dx > dy) {
					nodes[i].h = 14*dy + 10*(dx - dy);
				} else {
					nodes[i].h = 14*dx + 10*(dy - dx);
				}*/
				nodes[i].h = dx+dy;
				nodes[i].f = nodes[i].g + nodes[i].h;
				openList.push_back(nodes[i]);
			}
		}
	} // while (!openList.empty())
	if (openList.empty()) {
		std::cerr << "Could not find a path to the destination\n";
		return false;
	}
	// start by adding the destination to the path
	path.push_back(dest);
	int parent_x = bestNode.parent_x;
	int parent_y = bestNode.parent_y;
	closedList.pop_back();
	// go backwards through closed list following parents to create path
	for (std::vector<PathNode>::iterator it = closedList.end()-1; it != closedList.begin();
			it--) {
		if ((it->x == parent_x) && (it->y == parent_y)) {
			sf::Vector2f nextPos((float)it->x*32, (float)it->y*32);
			path.push_back(nextPos);
			parent_x= it->parent_x;
			parent_y= it->parent_y;
		}
	}
	std::reverse(path.begin(), path.end());
//	std::cout << path.size() << std::endl;
	return true;
}

// This algorithm checks if the character collides with any
// other objects in navigate mode.
// There will be a separate algorithm for battle mode
// I could organize all of the objects into quadtrees but there aren't
// too many objects to keep track of so this should be efficient enough
bool ObjectManager::collisionCheck(OForce_Object::Character *curChar, int characterIndex)
{
  sf::FloatRect charRect = curChar->sprite.getGlobalBounds();
  for (int i = 0; i < collisionRects.size(); i++) {
    if (charRect.intersects(collisionRects[i])) {
	  return true;
    }
  }
  if (curChar->getCollide()) {
	  for (int i = characterIndex; i < vCharacters.size(); i++) {
		  if (curChar != vCharacters[i]) {
			  if (vCharacters[i]->getCollide()) {
				  sf::FloatRect otherRect = vCharacters[i]->sprite.getGlobalBounds();
				  if (charRect.intersects(otherRect)) {
					  return true;
				  }
			  }
		  }
	  }
	  for (int i = 0; i < vTreasure.size(); i++) {
		  sf::FloatRect otherRect = vTreasure[i]->sprite.getGlobalBounds();
		  if (charRect.intersects(otherRect)) {
			  return true;
		  }
	  }
	  // For polygons, which are not rectangles use separating axis theorem
  }
  return false;
}

COLLISION_TYPE ObjectManager::collisionCheck(OForce_Object::Character *curChar, float x, float y)
{
  sf::FloatRect charRect = curChar->sprite.getGlobalBounds();
  charRect.left   = x;
  charRect.top = y;
  for (int i = 0; i < collisionRects.size(); i++) {
    if (charRect.intersects(collisionRects[i])) {
	  return STATIC;
    }
  }
  if (curChar->getCollide()) {
	  for (int i = 0; i < vCharacters.size(); i++) {
		  if (curChar != vCharacters[i]) {
			  if (vCharacters[i]->getCollide()) {
				  sf::FloatRect otherRect = vCharacters[i]->sprite.getGlobalBounds();
				  if (charRect.intersects(otherRect)) {
					  return CHARACTER;
				  }
			  }
		  }
	  }
	  for (int i = 0; i < vTreasure.size(); i++) {
		  sf::FloatRect otherRect = vTreasure[i]->sprite.getGlobalBounds();
		  if (charRect.intersects(otherRect)) {
			  return STATIC;
		  }
	  }
	  // For polygons, which are not rectangles use separating axis theorem
  }
  return NONE;
}


} // namespace OForce_Mode



